package ace.cmp.security.oauth2.resource.server.core.service.impl;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.util.StringUtils;

/**
 * @author caspar
 * @date 2023/9/1 15:12
 */
@Slf4j
public class Oauth2ResourceServerSecurityServiceLoginSupplier implements Supplier<Authentication> {
  private final HttpServletRequest request;
  private final HttpServletResponse response;
  private final BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
  private final AuthenticationProvider authenticationProvider;

  public Oauth2ResourceServerSecurityServiceLoginSupplier(
      HttpServletRequest request,
      HttpServletResponse response,
      AuthenticationProvider authenticationProvider) {
    this.request = request;
    this.response = response;
    this.authenticationProvider = authenticationProvider;
  }

  @Override
  public Authentication get() {
    String token;
    try {
      token = this.bearerTokenResolver.resolve(request);
    } catch (OAuth2AuthenticationException invalid) {
      this.log.trace(
          "Sending to authentication entry point since failed to resolve bearer token", invalid);
      return null;
    }
    if (StringUtils.hasText(token) == false) {
      return null;
    }

    BearerTokenAuthenticationToken authenticationRequest =
        new BearerTokenAuthenticationToken(token);

    try {
      Authentication authenticationResult =
          authenticationProvider.authenticate(authenticationRequest);
      SecurityContext context = SecurityContextHolder.createEmptyContext();
      context.setAuthentication(authenticationResult);
      SecurityContextHolder.setContext(context);
      return authenticationResult;
    } catch (AuthenticationException failed) {
      this.log.trace("Failed to process authentication input", failed);
      return null;
    }
  }
}
